package evolutionaryLoop;

import java.text.DecimalFormat;
import java.util.LinkedList;
import java.util.List;

import statistics.PopulationStatistics;
import evolutionaryProblems.Phenotype;
import evolutionaryProblems.Problem;
import evolutionaryProblems.RunningStatus;


public class EvolutionaryLoop {

	private DecimalFormat decFormat = new DecimalFormat("0.000");
	private EvolutionaryParameters parameters; 
	private Population population; 
	private Phenotype bestSolution; 
	private int generation = 0;
	private List<PopulationStatistics> populationLog = new LinkedList<PopulationStatistics>(); 
	private Population elite = new Population(0);
	private Problem problem;
//	private boolean pause; 
//	private volatile boolean kill = false;
//	private boolean newBest = true; 
	private volatile RunningStatus status; 
	
	public EvolutionaryLoop(EvolutionaryParameters parameters, Population population, Problem problem) {
		this.problem = problem;
		this.parameters = parameters; 
		this.population = population;
		init();
	}
	
	public void runLoop() {
		new Loop().start();
	}
	
	public void nextStep() {
		parameters.getFitnessFunction().nextGeneration(); 
		Population children;
		children = population.nextGeneration(parameters); 
		children.cull(parameters.getCulling()); 
		population = parameters.getAdultSelectionProtocol().selection(children,population); 
		recalculateElite(); 
		children.merge(elite); 
		elite = population.getBest(parameters.getElitism());
		reviewGeneration();
	}
	
	private void recalculateElite() {
		for (Phenotype phenotype : elite) {
			phenotype.calculateFitness(); 
		}
	}

	public void setPause(boolean pause) {
		if (pause) {
			status = RunningStatus.PAUSED; 
		}else {
			status = RunningStatus.RUNNING; 
		}
	}


	private boolean loopCondition(){
		return parameters.getIterationsAndDecrement()>0 && !population.isSolved();
	}

	private void init() {
//		System.out.println(parameters);
//		System.out.println("**************************************************************************************************");
//		System.out.println("Generation " + 
//				" \tBest" + 
//				" \tWorst" + 
//				" \tAvg" + 
//				" \tSD" + 
//				" \tBest solution" );

	}
	
	

	private void reviewGeneration(){
		generation ++; 
		PopulationStatistics populationStatistics = population.getStatistics();
		populationStatistics.setGeneration(generation); 
		problem.updateStatistics(populationStatistics); 
		populationLog.add(populationStatistics);
		
		if (populationStatistics.getBest().compareTo(bestSolution) > 0 || bestSolution == null) {
			bestSolution = populationStatistics.getBest(); 
//			this.newBest = true; 
		}
		
		printStatistics(populationStatistics);
//		if (newBest && parameters.isViewMap()){
//			parameters.getFitnessFunction().showPhenotype(populationStatistics.getBest()); 
//			newBest = false; 
//		}
		
	}


	private void printStatistics(PopulationStatistics populationStatistics) {
		double bestInGeneration = populationStatistics.getBestFitness(); 
		double worstInGeneration = populationStatistics.getWorstFitness(); 
		double avgFitness = populationStatistics.getAvgFitness();
		double standardDiviation = populationStatistics.getStandardDiviation(); 
		
//		System.out.println("  " + generation + 
//				" \t\t" + decFormat.format(bestInGeneration) + 
//				" \t" + decFormat.format(worstInGeneration) + 
//				" \t" + decFormat.format(avgFitness) + 
//				" \t" + decFormat.format(standardDiviation) + 
//				" \t" + bestSolution);
	}
	
	class Loop extends Thread {
		public void run() {
			status = RunningStatus.RUNNING; 
			
//			while (!pause && loopCondition() && !kill)
			while (loopCondition() && status == RunningStatus.RUNNING){
				nextStep();
			}
			if (!loopCondition()){
				parameters.getWriteStatistics().writeStatistics(populationLog);
//				parameters.getFitnessFunction().showPhenotype(bestSolution);
			}
//			kill = false; 
			status = RunningStatus.FINISHED; 
		}
	}

	public void kill(){
		if (status == RunningStatus.FINISHED){
			return; 
		} else if (status == RunningStatus.PAUSED){
			status = RunningStatus.FINISHED; 
		}
		status = RunningStatus.TERMINATING; 
	}
	
	public boolean haveKilled(){
		return status == RunningStatus.FINISHED; 
	}

}
